#!/usr/bin/python3.11
# -*- coding: utf-8 -*-

import os
import sys
import datetime

"""
PowerManagement port from https://github.com/Kentzo/Power.git
only use linux part for myself
"""

POWER_SUPPLY_PATH = '/sys/class/power_supply'
if not os.access(POWER_SUPPLY_PATH, os.R_OK):
    raise RuntimeError("Unable to read {path}.".format(path=POWER_SUPPLY_PATH))

class CommonPower(object):
    POWER_TYPE_AC = 0
    POWER_TYPE_BATTERY = 1
    POWER_TYPE_UPS = 2
    LOW_BATTERY_WARNING_NONE = 1
    LOW_BATTERY_WARNING_EARLY = 2
    LOW_BATTERY_WARNING_FINAL = 3
    TIME_REMAINING_UNKNOWN = -1.0
    TIME_REMAINING_UNLIMITED = -2.0
common = CommonPower()

class PowerManagement(object):
    @staticmethod
    def power_source_type(supply_path):
        """
        @param supply_path: Path to power supply
        @return: One of common.POWER_TYPE_*
        @raise: Runtime error if type of power source is not supported
        """
        with open(os.path.join(supply_path, 'type'), 'r') as type_file:
            type = type_file.readline().strip()
            if type == 'Mains':
                return common.POWER_TYPE_AC
            elif type == 'UPS':
                return common.POWER_TYPE_UPS
            elif type == 'Battery':
                return common.POWER_TYPE_BATTERY
            else:
                raise RuntimeError("Type of {path} ({type}) is not supported".format(path=supply_path, type=type))

    @staticmethod
    def is_ac_online(supply_path):
        """
        @param supply_path: Path to power supply
        @return: True if ac is online. Otherwise False
        """
        with open(os.path.join(supply_path, 'online'), 'r') as online_file:
            return online_file.readline().strip() == '1'

    @staticmethod
    def is_battery_present(supply_path):
        """
        @param supply_path: Path to power supply
        @return: True if battery is present. Otherwise False
        """
        with open(os.path.join(supply_path, 'present'), 'r') as present_file:
            return present_file.readline().strip() == '1'

    @staticmethod
    def is_battery_discharging(supply_path):
        """
        @param supply_path: Path to power supply
        @return: True if ac is online. Otherwise False
        """
        with open(os.path.join(supply_path, 'status'), 'r') as status_file:
            return status_file.readline().strip() == 'Discharging'

    @staticmethod
    def get_battery_state(supply_path):
        """
        @param supply_path: Path to power supply
        @return: Tuple (energy_full, energy_now, power_now)
        """
        try:
            energy_now_file = open(os.path.join(supply_path, 'energy_now'), 'r')
        except IOError:
            energy_now_file = open(os.path.join(supply_path, 'charge_now'), 'r')

        try:
            energy_full_file = open(os.path.join(supply_path, 'energy_full'), 'r')
        except IOError:
            energy_full_file = open(os.path.join(supply_path, 'charge_full'), 'r')

        with energy_now_file:
            with open(os.path.join(supply_path, 'power_now'), 'r') as power_now_file:
                with energy_full_file:
                    energy_now = float(energy_now_file.readline().strip())
                    power_now = float(power_now_file.readline().strip())
                    energy_full = float(energy_full_file.readline().strip())
                    return energy_full, energy_now, power_now

    def get_providing_power_source_type(self):
        """
        Looks through all power supplies in POWER_SUPPLY_PATH.
        If there is an AC adapter online returns POWER_TYPE_AC.
        If there is a discharging battery, returns POWER_TYPE_BATTERY.
        Since the order of supplies is arbitrary, whatever found first is returned.
        """
        for supply in os.listdir(POWER_SUPPLY_PATH):
            supply_path = os.path.join(POWER_SUPPLY_PATH, supply)
            try:
                type = self.power_source_type(supply_path)
                if type == common.POWER_TYPE_AC:
                    if self.is_ac_online(supply_path):
                        return common.POWER_TYPE_AC
                elif type == common.POWER_TYPE_BATTERY:
                    if self.is_battery_present(supply_path) and self.is_battery_discharging(supply_path):
                        return common.POWER_TYPE_BATTERY
                else:
                    warnings.warn("UPS is not supported.")
            except (RuntimeError, IOError) as e:
                warnings.warn("Unable to read properties of {0}: {1}".format(supply_path, e), category=RuntimeWarning)

        return common.POWER_TYPE_AC

    def get_low_battery_warning_level(self):
        """
        Looks through all power supplies in POWER_SUPPLY_PATH.
        If there is an AC adapter online returns POWER_TYPE_AC returns LOW_BATTERY_WARNING_NONE.
        Otherwise determines total percentage and time remaining across all attached batteries.
        """
        all_energy_full = []
        all_energy_now = []
        all_power_now = []
        for supply in os.listdir(POWER_SUPPLY_PATH):
            supply_path = os.path.join(POWER_SUPPLY_PATH, supply)
            try:
                type = self.power_source_type(supply_path)
                if type == common.POWER_TYPE_AC:
                    if self.is_ac_online(supply_path):
                        return common.LOW_BATTERY_WARNING_NONE
                elif type == common.POWER_TYPE_BATTERY:
                    if self.is_battery_present(supply_path) and self.is_battery_discharging(supply_path):
                        energy_full, energy_now, power_now = self.get_battery_state(supply_path)
                        all_energy_full.append(energy_full)
                        all_energy_now.append(energy_now)
                        all_power_now.append(power_now)
                else:
                    warnings.warn("UPS is not supported.")
            except (RuntimeError, IOError) as e:
                warnings.warn("Unable to read properties of {0}: {1}".format(supply_path, e), category=RuntimeWarning)

        try:
            total_percentage = sum(all_energy_full) / sum(all_energy_now)
            total_time = sum([energy_now / power_now * 60.0 for energy_now, power_now in zip(all_energy_now, all_power_now)])
            if total_time <= 10.0:
                return common.LOW_BATTERY_WARNING_FINAL
            elif total_percentage <= 22.0:
                return common.LOW_BATTERY_WARNING_EARLY
            else:
                return common.LOW_BATTERY_WARNING_NONE
        except ZeroDivisionError as e:
            warnings.warn("Unable to calculate low battery level: {0}".format(e), category=RuntimeWarning)
            return common.LOW_BATTERY_WARNING_NONE

    def get_time_remaining_estimate(self):
        """
        Looks through all power sources and returns total time remaining estimate
        or TIME_REMAINING_UNLIMITED if ac power supply is online.
        """
        all_energy_now = []
        all_energy_not_discharging = []
        all_power_now = []
        for supply in os.listdir(POWER_SUPPLY_PATH):
            supply_path = os.path.join(POWER_SUPPLY_PATH, supply)
            try:
                type = self.power_source_type(supply_path)
                if type == common.POWER_TYPE_AC:
                    if self.is_ac_online(supply_path):
                        return common.TIME_REMAINING_UNLIMITED
                elif type == common.POWER_TYPE_BATTERY:
                    if self.is_battery_present(supply_path) and self.is_battery_discharging(supply_path):
                        energy_full, energy_now, power_now = self.get_battery_state(supply_path)
                        all_energy_now.append(energy_now)
                        all_power_now.append(power_now)
                    elif self.is_battery_present(supply_path) and not self.is_battery_discharging(supply_path):
                        energy_now = self.get_battery_state(supply_path)[1]
                        all_energy_not_discharging.append(energy_now)
                else:
                    warnings.warn("UPS is not supported.")
            except (RuntimeError, IOError) as e:
                warnings.warn("Unable to read properties of {0}: {1}".format(supply_path, e), category=RuntimeWarning)

        if len(all_energy_now) > 0:
            try:
                return sum([energy_now / power_now * 60.0 for energy_now, power_now in zip(all_energy_now, all_power_now)])\
                    + sum(all_energy_not_discharging) / (sum(all_power_now) / len(all_power_now)) * 60.0
            except ZeroDivisionError as e:
                warnings.warn("Unable to calculate time remaining estimate: {0}".format(e), category=RuntimeWarning)
                return common.TIME_REMAINING_UNKNOWN
        else:
            return common.TIME_REMAINING_UNKNOWN

def power_info():
    # import power
    # level = power.PowerManagement().get_low_battery_warning_level()
    # print level
    power_type = PowerManagement().get_providing_power_source_type()
    estimate = PowerManagement().get_time_remaining_estimate()
    res = "AC: %s %.2fh"%(
        "on" if power_type==common.POWER_TYPE_AC else "off", 
        estimate/60
    )
    return res

from PyQt6.QtWidgets import  QApplication,QMainWindow,QWidget,QPushButton, QLabel
from PyQt6.QtCore import QTimer, Qt, QRect
from PyQt6.QtGui import QBitmap, QPainter, QColor, QFont, QPen, QRegion

class MyOverlayInfo(object):

    def __init__(self):
        self.app = QApplication(sys.argv)
        self.widget = QWidget()
        # for pyqt5
        # self.widget.setWindowFlags(
        #     Qt.WindowStaysOnTopHint
        #     | Qt.X11BypassWindowManagerHint # 这个和下面的有什么区别呢
        #     | Qt.BypassWindowManagerHint
        #     | Qt.FramelessWindowHint
        # )
        self.widget.setWindowFlags(
            Qt.WindowType.WindowStaysOnTopHint
            | Qt.WindowType.X11BypassWindowManagerHint # 这个和下面的有什么区别呢
            | Qt.WindowType.BypassWindowManagerHint
            | Qt.WindowType.FramelessWindowHint
        )
        

        width, height = 90, 57
        # ref: https://blog.csdn.net/oadgadgad/article/details/122496010
        desktop = QApplication.screens()[0]
        screen_width = desktop.availableGeometry().width()
        # for qt5
        # desktop = QApplication.desktop()
        # screen_width = desktop.width()

        # 显示坐标设置, 右上角, 一般的toolbar下面一点
        x, y = screen_width - width - 140, 0

        self.widget.resize(width, height)
        self.widget.move(x, y)

        self.widget.setStyleSheet("background-color:green;")

        # 背景透明
        self.bitmap = QBitmap(self.widget.width(), self.widget.height())
        self.painter = QPainter(self.bitmap)
        font = QFont()
        # font.setPointSize(14)
        font.setFamily('Times New Roman')
        self.painter.setFont(font)

        # 抗锯齿,但是好像没有生效
        # self.painter.setRenderHint(QPainter.TextAntialiasing, True)

        # painter.setRenderHints(QPainter.TextAntialiasing|QPainter.HighQualityAntialiasing)
        # etRenderHint(QPainter::TextAntialiasing, true) 

        # self.region = QRegion(0, 0, self.widget.width()/2, self.widget.height())
        # painter = QPainter(self.region)
        # painter.setPen(QColor(255, 255, 255))
        # painter.drawRect(0, 0, self.widget.width()/2, self.widget.height())

        self.detected_timer_even_min = False
        self.widget.show()
        self.refresh()
        self.refresh_time = 1000 * 2 # 10 seconds

    def start(self):
        self.timer = QTimer(self.widget)
        self.timer.timeout.connect(self.update)
        self.timer.start(self.refresh_time) # every 30 second
        self.app.exec()
        # for pyqt5
        # self.app.exec_()

    def week_day(self, date):
        week_day_dict = {
            0 : '周一',
            1 : '周二',
            2 : '周三',
            3 : '周四',
            4 : '周五',
            5 : '周六',
            6 : '周天',
        }
        day = date.weekday()
        return week_day_dict[day]

    def mask_set(self, show_str):
        painter = self.painter

        painter.setPen(QColor(255, 255, 255))
        painter.drawRect(0, 0, self.widget.width(), self.widget.height())

        painter.setPen(QColor(0, 0, 0))
        font_height = 15
        x, y = 1, 15
        for line in show_str.split("\n"):
            painter.drawText(x, y, line)
            y += font_height
        self.widget.setMask(self.bitmap)
        self.widget.update()

    def detected_even_miniut(self):
        time = datetime.datetime.now()
        if time.second != 0 or True:
            self.timer.stop()
            self.timer.start(1000)
            self.detected_timer_even_min = False
            return
        if not self.detected_timer_even_min:
            if time.second == 0:
                self.detected_timer_even_min = True
                self.timer.stop()
                self.timer.start(1000*60) # 1min

    def refresh(self):
        tt_time = datetime.datetime.now()
        show_str = "%s %s\n%s"%(
            tt_time.strftime("%Y-%m-%d\n%H:%M:%S"),
            # tt_time.strftime("%Y-%m-%d\n%H:%M"),
            self.week_day(tt_time),
            power_info()
        )
        self.mask_set(show_str)

    def update(self):
        self.refresh()
        # self.detected_even_miniut()

cfg_msg="""
openSUSE KDE桌面自动启动配置： .config/autostart/pc-info-top.py.desktop

[Desktop Entry]
Exec=pc-info-top.py
Icon=dialog-scripts
Name=pc-info-top.py
Path=
Type=Application
X-KDE-AutostartScript=true

"""

def cfg_auto_start_kde():
    boot_cfg = """
[Desktop Entry]
Exec=%s
Icon=dialog-scripts
Name=pc-info-top.py
Path=
StartupNotify=true
Terminal=false
TerminalOptions=
Type=Application
X-KDE-AutostartScript=true
X-KDE-RunOnDiscreteGpu=false
X-KDE-SubstituteUID=false
X-KDE-Username=
"""%(os.path.abspath(__file__))

    boot_file = ".config/autostart/pc-info-top.py.desktop"
    with open(boot_file, "w") as ofs:
        ofs.write(boot_cfg)
    print("finish set auto start -> [%s]"%(boot_file))

def usage():
    ustr = """
    %s [set_auto_start]
    """%(__file__)
    print(ustr)

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "set_auto_start":
        cfg_auto_start_kde()
    else:
        usage()
        print(cfg_msg)
        t = MyOverlayInfo()
        t.start()        
